Skip to content

feat(tech-spec): devex revamp#1914

Open
sergiofilhowz wants to merge 3 commits into
mainfrom
feat/tech-spec/2026-06-devex
Open

feat(tech-spec): devex revamp#1914
sergiofilhowz wants to merge 3 commits into
mainfrom
feat/tech-spec/2026-06-devex

Conversation

@sergiofilhowz

@sergiofilhowz sergiofilhowz commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • Added a new interactive tech-spec presentation site with hash-based navigation, animated diagrams, a CLI playground, and light/dark theming.
    • Added a gallery page to browse and open available tech-spec presentations.
  • Documentation
    • Expanded the tech-specs library with new developer-experience, migration, configuration/bootstrap, engine/gateway, lifecycle/onboarding, CLI architecture, secrets, and process-daemon specs—plus the supporting presentation content.
  • Chores
    • Updated tech-specs build/deployment setup, added ignore rules for local artifacts, and set the tech-specs Node.js version to 24.

@vercel

vercel Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
iii-website Ready Ready Preview, Comment Jun 29, 2026 8:50pm
tech-spec Ready Ready Preview, 💬 10 unresolved
✅ 1 resolved
Jun 29, 2026 8:50pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0980f251-036d-441f-b8a3-fd747bc76240

📥 Commits

Reviewing files that changed from the base of the PR and between c60c1a4 and 5a34eb0.

⛔ Files ignored due to path filters (3)
  • tech-specs/2026-06-devexp/presentation/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • tech-specs/_gallery/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • tech-specs/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (96)
  • tech-specs/.gitignore
  • tech-specs/.nvmrc
  • tech-specs/2026-06-devexp/README.md
  • tech-specs/2026-06-devexp/cli-and-functions.md
  • tech-specs/2026-06-devexp/configuration-and-bootstrap.md
  • tech-specs/2026-06-devexp/devex-review-improvements.md
  • tech-specs/2026-06-devexp/engine-and-gateway.md
  • tech-specs/2026-06-devexp/lifecycle-and-onboarding.md
  • tech-specs/2026-06-devexp/migration.md
  • tech-specs/2026-06-devexp/presentation/.gitignore
  • tech-specs/2026-06-devexp/presentation/README.md
  • tech-specs/2026-06-devexp/presentation/index.html
  • tech-specs/2026-06-devexp/presentation/package.json
  • tech-specs/2026-06-devexp/presentation/src/App.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/Footer.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/PageShell.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/PlayerControls.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/Section.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/SpecSheet.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/TopNav.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/diagrams/CliPlayground.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/diagrams/DaemonModel.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/diagrams/MeetingPointsSequence.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/diagrams/ReadinessGraph.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/diagrams/SystemMap.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/Button.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/Caret.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/Cell.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/CodeBlock.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/FnChip.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/ModeToggle.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/Prompt.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/Sheet.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/StatusDot.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/StatusPanel.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/Terminal.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/Wordmark.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/WorkerCard.tsx
  • tech-specs/2026-06-devexp/presentation/src/content/changes.ts
  • tech-specs/2026-06-devexp/presentation/src/content/map.ts
  • tech-specs/2026-06-devexp/presentation/src/content/playground.ts
  • tech-specs/2026-06-devexp/presentation/src/hooks/useHashRoute.ts
  • tech-specs/2026-06-devexp/presentation/src/hooks/useStepper.ts
  • tech-specs/2026-06-devexp/presentation/src/hooks/useTheme.ts
  • tech-specs/2026-06-devexp/presentation/src/index.css
  • tech-specs/2026-06-devexp/presentation/src/lib/utils.ts
  • tech-specs/2026-06-devexp/presentation/src/main.tsx
  • tech-specs/2026-06-devexp/presentation/src/pages/ComposePage.tsx
  • tech-specs/2026-06-devexp/presentation/src/pages/MigrationPage.tsx
  • tech-specs/2026-06-devexp/presentation/src/pages/PlaygroundPage.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/ComposeSection.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/ConfigSection.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/DaemonSection.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/Hero.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/MeetingPointsSection.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/PayoffSection.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/PlaygroundSection.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/ReadinessSection.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/RemovedSection.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/SystemMapSection.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/WhySection.tsx
  • tech-specs/2026-06-devexp/presentation/src/vite-env.d.ts
  • tech-specs/2026-06-devexp/presentation/tsconfig.app.json
  • tech-specs/2026-06-devexp/presentation/tsconfig.json
  • tech-specs/2026-06-devexp/presentation/tsconfig.node.json
  • tech-specs/2026-06-devexp/presentation/vite.config.ts
  • tech-specs/2026-06-devexp/process-daemon.md
  • tech-specs/2026-06-devexp/secrets.md
  • tech-specs/2026-06-devexp/worker-compose.md
  • tech-specs/README.md
  • tech-specs/_gallery/.gitignore
  • tech-specs/_gallery/index.html
  • tech-specs/_gallery/package.json
  • tech-specs/_gallery/src/App.tsx
  • tech-specs/_gallery/src/components/Gallery.tsx
  • tech-specs/_gallery/src/components/PresentationCard.tsx
  • tech-specs/_gallery/src/components/SiteFooter.tsx
  • tech-specs/_gallery/src/components/SiteHeader.tsx
  • tech-specs/_gallery/src/components/schematic/ModeToggle.tsx
  • tech-specs/_gallery/src/components/schematic/Prompt.tsx
  • tech-specs/_gallery/src/components/schematic/Sheet.tsx
  • tech-specs/_gallery/src/components/schematic/StatusDot.tsx
  • tech-specs/_gallery/src/components/schematic/Wordmark.tsx
  • tech-specs/_gallery/src/content/presentations.ts
  • tech-specs/_gallery/src/hooks/useTheme.ts
  • tech-specs/_gallery/src/index.css
  • tech-specs/_gallery/src/lib/utils.ts
  • tech-specs/_gallery/src/main.tsx
  • tech-specs/_gallery/src/vite-env.d.ts
  • tech-specs/_gallery/tsconfig.app.json
  • tech-specs/_gallery/tsconfig.json
  • tech-specs/_gallery/tsconfig.node.json
  • tech-specs/_gallery/vite.config.ts
  • tech-specs/build.mjs
  • tech-specs/package.json
  • tech-specs/vercel.json
✅ Files skipped from review due to trivial changes (13)
  • tech-specs/.nvmrc
  • tech-specs/_gallery/tsconfig.json
  • tech-specs/_gallery/index.html
  • tech-specs/2026-06-devexp/presentation/src/vite-env.d.ts
  • tech-specs/2026-06-devexp/presentation/tsconfig.app.json
  • tech-specs/_gallery/tsconfig.node.json
  • tech-specs/2026-06-devexp/presentation/.gitignore
  • tech-specs/.gitignore
  • tech-specs/2026-06-devexp/presentation/tsconfig.json
  • tech-specs/_gallery/.gitignore
  • tech-specs/vercel.json
  • tech-specs/2026-06-devexp/presentation/README.md
  • tech-specs/2026-06-devexp/presentation/src/content/map.ts
🚧 Files skipped from review as they are similar to previous changes (70)
  • tech-specs/2026-06-devexp/presentation/vite.config.ts
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/Prompt.tsx
  • tech-specs/2026-06-devexp/presentation/package.json
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/Sheet.tsx
  • tech-specs/_gallery/src/components/SiteFooter.tsx
  • tech-specs/_gallery/src/lib/utils.ts
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/StatusDot.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/WhySection.tsx
  • tech-specs/_gallery/src/vite-env.d.ts
  • tech-specs/_gallery/src/components/Gallery.tsx
  • tech-specs/2026-06-devexp/presentation/src/pages/PlaygroundPage.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/Caret.tsx
  • tech-specs/_gallery/src/components/PresentationCard.tsx
  • tech-specs/2026-06-devexp/presentation/src/hooks/useTheme.ts
  • tech-specs/_gallery/src/components/schematic/ModeToggle.tsx
  • tech-specs/_gallery/src/main.tsx
  • tech-specs/_gallery/vite.config.ts
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/ModeToggle.tsx
  • tech-specs/_gallery/tsconfig.app.json
  • tech-specs/2026-06-devexp/presentation/src/main.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/MeetingPointsSection.tsx
  • tech-specs/_gallery/src/components/schematic/Prompt.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/Footer.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/Cell.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/diagrams/DaemonModel.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/Section.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/Wordmark.tsx
  • tech-specs/package.json
  • tech-specs/2026-06-devexp/presentation/src/sections/PayoffSection.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/FnChip.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/Terminal.tsx
  • tech-specs/_gallery/src/components/SiteHeader.tsx
  • tech-specs/_gallery/src/content/presentations.ts
  • tech-specs/2026-06-devexp/presentation/src/components/diagrams/ReadinessGraph.tsx
  • tech-specs/2026-06-devexp/presentation/src/App.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/Hero.tsx
  • tech-specs/2026-06-devexp/presentation/src/lib/utils.ts
  • tech-specs/_gallery/src/App.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/TopNav.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/PlayerControls.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/StatusPanel.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/WorkerCard.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/CodeBlock.tsx
  • tech-specs/_gallery/src/components/schematic/Sheet.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/SystemMapSection.tsx
  • tech-specs/2026-06-devexp/presentation/tsconfig.node.json
  • tech-specs/2026-06-devexp/presentation/src/sections/ConfigSection.tsx
  • tech-specs/_gallery/package.json
  • tech-specs/2026-06-devexp/presentation/src/sections/RemovedSection.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/DaemonSection.tsx
  • tech-specs/_gallery/src/components/schematic/StatusDot.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/ReadinessSection.tsx
  • tech-specs/_gallery/src/components/schematic/Wordmark.tsx
  • tech-specs/_gallery/src/hooks/useTheme.ts
  • tech-specs/2026-06-devexp/presentation/src/components/diagrams/SystemMap.tsx
  • tech-specs/2026-06-devexp/presentation/index.html
  • tech-specs/2026-06-devexp/presentation/src/pages/MigrationPage.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/ComposeSection.tsx
  • tech-specs/2026-06-devexp/presentation/src/content/playground.ts
  • tech-specs/2026-06-devexp/devex-review-improvements.md
  • tech-specs/2026-06-devexp/presentation/src/components/diagrams/MeetingPointsSequence.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/PlaygroundSection.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/SpecSheet.tsx
  • tech-specs/2026-06-devexp/presentation/src/hooks/useHashRoute.ts
  • tech-specs/2026-06-devexp/presentation/src/components/PageShell.tsx
  • tech-specs/2026-06-devexp/presentation/src/hooks/useStepper.ts
  • tech-specs/2026-06-devexp/presentation/src/content/changes.ts
  • tech-specs/2026-06-devexp/presentation/src/components/diagrams/CliPlayground.tsx
  • tech-specs/build.mjs
  • tech-specs/2026-06-devexp/presentation/src/pages/ComposePage.tsx

📝 Walkthrough

Walkthrough

This PR adds the tech-specs workspace scaffolding, a gallery site, a new devexp presentation app, and markdown specs covering the architecture, runtime, migration, and rollout model.

Changes

Tech-specs Overhaul

Layer / File(s) Summary
Repository scaffold and build pipeline
tech-specs/.gitignore, tech-specs/.nvmrc, tech-specs/package.json, tech-specs/README.md, tech-specs/build.mjs, tech-specs/vercel.json
Root packaging, ignore rules, build orchestration, deployment settings, Node pinning, and workspace docs are added for the tech-specs site.
Gallery app shell and deck index
tech-specs/_gallery/...
The gallery app adds its Vite/React scaffold, theme and layout primitives, content catalog, presentation cards, and homepage shell.
Presentation scaffold and content
tech-specs/2026-06-devexp/presentation/...
The devexp presentation adds its Vite/React scaffold, theming and routing hooks, base stylesheet, and static content banks for the deck views.
Shared presentation UI
tech-specs/2026-06-devexp/presentation/src/components/schematic/*, tech-specs/2026-06-devexp/presentation/src/components/diagrams/*
Reusable schematic components and diagram widgets render the shell, terminal, chips, status, readiness, map, and sequence visuals.
Presentation pages and sections
tech-specs/2026-06-devexp/presentation/src/App.tsx, tech-specs/2026-06-devexp/presentation/src/pages/*, tech-specs/2026-06-devexp/presentation/src/sections/*
The presentation app routes between home, playground, compose, and migration views and assembles the home sections.
Compose and configuration docs
tech-specs/2026-06-devexp/README.md, tech-specs/2026-06-devexp/worker-compose.md, tech-specs/2026-06-devexp/configuration-and-bootstrap.md, tech-specs/2026-06-devexp/secrets.md
The docs define worker-compose.yml, bootstrap configuration storage, and secret-handling rules.
Runtime, command, migration, and review docs
tech-specs/2026-06-devexp/engine-and-gateway.md, tech-specs/2026-06-devexp/process-daemon.md, tech-specs/2026-06-devexp/cli-and-functions.md, tech-specs/2026-06-devexp/lifecycle-and-onboarding.md, tech-specs/2026-06-devexp/migration.md, tech-specs/2026-06-devexp/devex-review-improvements.md
The runtime docs define engine gateway behavior, process supervision, CLI function routing, onboarding flow, migration rollout phases, and the DX review plan.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Suggested reviewers

  • ytallo
  • guibeira

Poem

🐇 I hopped through specs and paths of light,
from gallery decks to terminals bright.
One worker-compose moon, one iii.lock star,
the pages bloom where brave bunny coders are.
Hoppity-huzzah! 🌿

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning No pull request description was provided, so the required What/Why/Notes template is completely missing. Add the template sections What, Why, and Notes, and briefly summarize the change, motivation, and any migration or review notes.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is generic and does not convey the specific technical changes in the pull request. Use a concise, specific title naming the main change, such as the devex tech-spec/presentation revamp or migration work.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/tech-spec/2026-06-devex

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (14)
tech-specs/README.md-9-33 (1)

9-33: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add language tags to the fenced blocks.

Lines 9, 22, and 31 use bare code fences, which trips markdownlint MD040. Tag the directory-tree examples as text and the command example as bash so the docs lint cleanly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/README.md` around lines 9 - 33, Add language tags to the fenced
examples in README content so markdownlint passes: update the directory-tree
code blocks to use a plain text language tag and the command example to use a
bash language tag. Locate the affected fences in the README sections describing
the tech-specs layout, built output, and the add-a-presentation command, and
make sure every fenced block in those examples has an explicit language
identifier.

Source: Linters/SAST tools

tech-specs/_gallery/src/hooks/useTheme.ts-15-19 (1)

15-19: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Apply the theme attribute before first paint.

readTheme() already knows the persisted theme synchronously, but data-theme is only written in an effect after mount. Returning dark-mode users will see a light-theme flash on every reload until that effect runs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/_gallery/src/hooks/useTheme.ts` around lines 15 - 19, The theme is
being applied too late because useTheme only writes
document.documentElement.dataset.theme inside useEffect after mount, causing a
flash on reload. Update useTheme so the persisted theme from readTheme() is
applied before first paint, ideally by setting the document root theme
synchronously during initialization or in a pre-paint hook, while keeping the
existing theme state managed by useState and referencing
useTheme/readTheme/document.documentElement.dataset.theme.
tech-specs/_gallery/src/index.css-4-7 (1)

4-7: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Quote the named fallback fonts.

Lines 4-7 are tripping value-keyword-case because the named fallbacks are unquoted. Quoting them preserves the intended family names and clears the lint errors.

Suggested fix
-  --font-sans: "Chivo Mono", ui-monospace, SFMono-Regular, Menlo, Monaco,
-    Consolas, "Liberation Mono", "Courier New", monospace;
-  --font-mono: "Chivo Mono", ui-monospace, SFMono-Regular, Menlo, Monaco,
-    Consolas, "Liberation Mono", "Courier New", monospace;
+  --font-sans: "Chivo Mono", ui-monospace, "SFMono-Regular", "Menlo", "Monaco",
+    "Consolas", "Liberation Mono", "Courier New", monospace;
+  --font-mono: "Chivo Mono", ui-monospace, "SFMono-Regular", "Menlo", "Monaco",
+    "Consolas", "Liberation Mono", "Courier New", monospace;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/_gallery/src/index.css` around lines 4 - 7, The CSS font stack in
the index stylesheet is using unquoted named fallback families, which is causing
the lint error. Update the font declarations for the variables in the root scope
so every named fallback in the stack is quoted consistently, while keeping the
same fallback order and the existing Chivo Mono primary family.

Source: Linters/SAST tools

tech-specs/2026-06-devexp/presentation/src/hooks/useStepper.ts-20-25 (1)

20-25: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clamp empty steppers before computing total - 1.

If total is ever 0, Lines 56 and 69 can set step to -1. Downstream consumers like MeetingPointsSequence index arrays with stepper.step, so this hook can publish an invalid state instead of a safe empty-step fallback.

Suggested fix
 export function useStepper(total: number, intervalMs = 2400, autoPlay = false): Stepper {
+  const maxStep = Math.max(total - 1, 0)
   const [step, setStep] = useState(0)
-  const [playing, setPlaying] = useState(autoPlay)
+  const [playing, setPlaying] = useState(autoPlay && total > 0)
   const timer = useRef<ReturnType<typeof setInterval> | null>(null)

-  const atEnd = step >= total - 1
+  const atEnd = step >= maxStep
@@
-        if (s >= total - 1) {
+        if (s >= maxStep) {
           setPlaying(false)
           return s
         }
@@
-    setStep((s) => (s >= total - 1 ? 0 : s))
+    setStep((s) => (s >= maxStep ? 0 : s))
     setPlaying(true)
-  }, [total])
+  }, [maxStep])
@@
-    setStep((s) => Math.min(total - 1, s + 1))
-  }, [total])
+    setStep((s) => Math.min(maxStep, s + 1))
+  }, [maxStep])
@@
-      setStep(Math.max(0, Math.min(total - 1, s)))
+      setStep(Math.max(0, Math.min(maxStep, s)))
     },
-    [total],
+    [maxStep],
   )

Also applies to: 54-70

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/presentation/src/hooks/useStepper.ts` around lines
20 - 25, The useStepper hook currently computes atEnd from total - 1 and can
drive step to -1 when total is 0, which leaks an invalid state to consumers like
MeetingPointsSequence. Update useStepper to explicitly handle the empty-step
case before any total - 1 logic, and ensure the reset/advance behavior in the
step-setting logic keeps step clamped to 0 when total is zero while preserving
the existing API around step, playing, and timer handling.
tech-specs/2026-06-devexp/presentation/src/sections/ConfigSection.tsx-103-106 (1)

103-106: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Scope the “no seed, no pointer” claim more narrowly.

The current copy overstates the contract. The documented bootstrap flow still has a seed-only worker config block and then replaces it with a breadcrumb to the persisted value, so this wording reads as contradictory unless you explicitly limit it to worker-compose.yml / iii.worker.yaml.

As per path instructions, config.yaml worker blocks are seed-only and later replaced with a breadcrumb to ./data/configuration/<id>.yaml.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/presentation/src/sections/ConfigSection.tsx` around
lines 103 - 106, The StatusPanel copy in ConfigSection is too broad and
conflicts with the documented bootstrap flow. Update the headline/detail text to
scope the “no seed, no pointer” claim only to worker-compose.yml and
iii.worker.yaml, and explicitly reflect that config.yaml worker blocks are
seed-only before being replaced with a breadcrumb to
./data/configuration/<id>.yaml. Use StatusPanel, headline, and detail as the
anchors when revising the wording.

Source: Path instructions

tech-specs/2026-06-devexp/presentation/src/pages/PlaygroundPage.tsx-60-62 (1)

60-62: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Align the JSON error envelope with the documented contract.

kind here conflicts with the established error shape and omits error_id. If consumers follow this slide, they’ll parse a field the HTTP/docs contract does not guarantee.

Suggested update
-          under --json, errors also emit {'{ error: { code, kind, message } }'}.
+          under --json, errors also emit {'{ error: { code, message, error_id? } }'}.

As per path instructions, docs/changelog/index.mdx and engine/src/workers/rest_api/README.md establish the stable envelope as {"error":{"code","message","error_id"?}}.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/presentation/src/pages/PlaygroundPage.tsx` around
lines 60 - 62, The JSON error example in PlaygroundPage should match the stable
documented error envelope instead of showing a non-contract field. Update the
text in the PlaygroundPage content so it reflects
{"error":{"code","message","error_id"?}} and remove the `kind` field from the
example, keeping the wording aligned with the contract used in the changelog and
REST API README.

Source: Path instructions

tech-specs/2026-06-devexp/presentation/src/pages/ComposePage.tsx-24-25 (1)

24-25: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use pnpm in the canonical worker scripts example.

This slide currently teaches npm install / npm run dev, which conflicts with the repo’s JS/TS package-manager standard and will drift from the rest of the devex material. The same snippet appears again in ComposeSection.tsx, so both copies should move together.

Suggested update
-        {'    '}scripts: {'{ '}install: npm install, start: npm run dev {'}'}{'\n'}
+        {'    '}scripts: {'{ '}install: pnpm install, start: pnpm run dev {'}'}{'\n'}

As per coding guidelines, use pnpm (never npm) for JS/TS packages.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/presentation/src/pages/ComposePage.tsx` around
lines 24 - 25, The canonical worker scripts example still uses npm commands,
which conflicts with the repo standard; update the example in ComposePage and
the matching snippet in ComposeSection to use pnpm consistently. Locate the
worker script text in the ComposePage component and the duplicated example in
ComposeSection, and replace the install/start commands so the slide teaches
pnpm-based workflows only.

Source: Coding guidelines

tech-specs/2026-06-devexp/presentation/src/components/SpecSheet.tsx-79-82 (1)

79-82: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve original casing in spec row descriptions.

This wrapper forces all row content to lowercase, which will mangle case-sensitive identifiers and examples in the tech-spec UI.

Suggested fix
-        <div className="mt-0.5 font-mono text-[12px] leading-[1.6] text-ink-faint lowercase">
+        <div className="mt-0.5 font-mono text-[12px] leading-[1.6] text-ink-faint">
           {children}
         </div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/presentation/src/components/SpecSheet.tsx` around
lines 79 - 82, The SpecSheet row description wrapper is forcing all child
content to lowercase, which breaks case-sensitive identifiers and examples.
Update the children rendering in SpecSheet so it preserves the original casing
by removing the lowercase styling from that div, while keeping the existing
typography and layout classes intact.
tech-specs/2026-06-devexp/presentation/src/components/TopNav.tsx-59-68 (1)

59-68: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Expose the active section to assistive tech.

Right now the current section is only indicated by color. Add aria-current="location" on the active link so screen readers receive the same state.

Suggested fix
               <a
                 key={link.id}
                 href={`#${link.id}`}
+                aria-current={active === link.id ? 'location' : undefined}
                 className={cn(
                   'font-mono text-[12px] lowercase py-1.5 transition-colors whitespace-nowrap',
                   active === link.id
                     ? 'text-accent'
                     : 'text-ink-faint hover:text-ink',
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/presentation/src/components/TopNav.tsx` around
lines 59 - 68, The active section is only distinguished visually in TopNav’s
link rendering, so add an accessible state to the active anchor in the link map.
Update the conditional rendering around the active link check in TopNav to set
aria-current="location" whenever active matches link.id, while leaving the
existing className styling and href behavior unchanged.
tech-specs/2026-06-devexp/presentation/src/index.css-204-221 (1)

204-221: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Honor reduced-motion for the blinking caret too.

Line 204 disables most motion, but not .blink. Since tech-specs/2026-06-devexp/presentation/src/components/schematic/Caret.tsx always applies that class, users with prefers-reduced-motion: reduce still get a continuous flashing animation.

Suggested fix
 `@media` (prefers-reduced-motion: reduce) {
   html {
     scroll-behavior: auto;
   }
+  .blink,
   .flow-dash,
   .flow-dash-slow,
   .ripple-ring,
   .pulse-dot,
   .wiggle,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/presentation/src/index.css` around lines 204 - 221,
The reduced-motion media query in the stylesheet disables several animations but
leaves the blinking caret active, so users can still see flashing motion. Update
the prefers-reduced-motion block in the CSS to also target the blink class used
by Caret so its animation is removed or neutralized when reduced motion is
requested, keeping the behavior consistent with the other motion-reduction
rules.
tech-specs/2026-06-devexp/presentation/src/content/changes.ts-342-348 (1)

342-348: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Include the other implicit roots in the readiness graph data.

ReadinessGraph only renders roots that exist in READINESS_NODES, but ReadinessSection later says configuration, iii-process-daemon, and iii-worker-ops are all valid always-ready depends_on targets. Keeping only configuration here makes the graph understate that contract. Add the missing roots or narrow the downstream copy.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/presentation/src/content/changes.ts` around lines
342 - 348, The readiness graph data only declares one always-ready root, but
ReadinessSection treats iii-process-daemon and iii-worker-ops as valid
always-ready depends_on targets too. Update READINESS_NODES in changes.ts to
include those missing implicit roots, or tighten the later copy so it only
mentions roots actually present in the graph. Keep the labels/notes consistent
with the existing readiness node entries so ReadinessGraph and the section text
agree.
tech-specs/2026-06-devexp/presentation/src/components/schematic/ModeToggle.tsx-23-45 (1)

23-45: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Drop the tab roles or implement real tab semantics.

Line 24 and Line 33 expose this as a tablist, but the buttons only use aria-pressed and click handling. Screen readers will announce tabs that don't behave like tabs. Either switch this to a plain button group, or add the missing tab behavior (aria-selected, roving focus, arrow-key navigation, and tabpanel wiring).

Suggested minimal fix if this is only a visual mode switch
     <div
-      role="tablist"
       className={cn('inline-flex border border-rule p-[2px]', className)}
     >
       {options.map((opt) => {
         const active = opt.value === value
         return (
           <button
             key={opt.value}
             type="button"
-            role="tab"
             aria-pressed={active}
             onClick={() => onChange(opt.value)}
             className={cn(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tech-specs/2026-06-devexp/presentation/src/components/schematic/ModeToggle.tsx`
around lines 23 - 45, The ModeToggle component is exposing tab semantics without
implementing real tabs. Update the ModeToggle render so it no longer uses
tablist/tab roles and aria-pressed on the buttons unless you add full tab
behavior; if this is just a visual mode switch, make it a plain button group
with the existing click handling and styling. Use the ModeToggle component, the
options.map rendering, and the button element as the main places to adjust the
accessibility semantics.
tech-specs/2026-06-devexp/presentation/src/components/schematic/Terminal.tsx-45-47 (1)

45-47: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Render falsy outputs explicitly.

output ? ... drops valid terminal payloads like 0 and empty strings, so those rows disappear instead of rendering.

Proposed fix
-      {output ? (
+      {output !== null && output !== undefined ? (
         <div className="pl-4 text-[12.5px] text-ink-faint">{output}</div>
       ) : null}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/presentation/src/components/schematic/Terminal.tsx`
around lines 45 - 47, The Terminal component is using a truthy check on output,
which hides valid falsy terminal values like 0 and empty strings. Update the
conditional rendering in Terminal to explicitly check for the presence of output
rather than its truthiness so that valid payloads still render, and keep the
existing output display block intact.
tech-specs/2026-06-devexp/presentation/src/components/diagrams/CliPlayground.tsx-77-89 (1)

77-89: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard the empty-track case.

This component dereferences tracks[0] in several places, so an empty array crashes the whole view on first render. Either require a non-empty tuple in the prop type or return a fallback before initializing state.

Proposed fix
 interface CliPlaygroundProps {
-  tracks: PlayTrack[]
+  tracks: [PlayTrack, ...PlayTrack[]]
   className?: string
   autoPlay?: boolean
 }
 export function CliPlayground({ tracks, className, autoPlay }: CliPlaygroundProps) {
+  if (tracks.length === 0) return null
   const [trackId, setTrackId] = useState(tracks[0].id)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tech-specs/2026-06-devexp/presentation/src/components/diagrams/CliPlayground.tsx`
around lines 77 - 89, CliPlayground currently assumes tracks always has at least
one item and immediately reads tracks[0], which will crash on an empty array.
Update CliPlaygroundProps and the CliPlayground component to either enforce a
non-empty tracks tuple at the type level or return a safe fallback before
calling useState/useMemo/useStepper. Make sure the guard happens before any
access to tracks[0], track.steps, or setTrackId initialization so the component
can render safely when no tracks are provided.
🧹 Nitpick comments (23)
tech-specs/2026-06-devexp/README.md (1)

338-345: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add language specifier to fenced code block.

The code block at line 338 lacks a language specifier. Add yaml or text to satisfy markdownlint and improve syntax highlighting.

- ```
+ ```yaml
  Phase 0  Dual-parser — compose lowered to today's EngineConfig (no behavior change, --compose flag)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/README.md` around lines 338 - 345, The fenced block
in the README phase list is missing a language specifier, so update the markdown
around the Phase 0–Phase 5 snippet to use an explicit fence like yaml or text.
Locate the block by the phase labels in the spec document and adjust the opening
fence only, keeping the existing content unchanged so markdownlint passes and
syntax highlighting is enabled.

Source: Linters/SAST tools

tech-specs/2026-06-devexp/lifecycle-and-onboarding.md (11)

179-184: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add text language hint to hot-reload output.

The iii logs -f stream output is plain text. Tag as text to satisfy MD040.

-```
+```text
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/lifecycle-and-onboarding.md` around lines 179 -
184, The hot-reload output example in the lifecycle/onboarding spec is missing
the `text` language hint required by MD040. Update the fenced code block in the
affected documentation so the `iii logs -f` output is explicitly marked as text,
using the existing example block as the target to fix.

107-118: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add yaml language hint to compose example.

The worker-compose.yml example at lines 107–118 lacks a language tag. Add yaml for syntax highlighting and MD040 compliance.

-```
+```yaml
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/lifecycle-and-onboarding.md` around lines 107 -
118, The compose example in the onboarding doc is missing the YAML language
hint, so update the fenced block around the worker-compose example to use the
same `yaml` tag already shown in the snippet; this is the only change needed for
the example associated with the worker compose configuration.

355-363: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add text language hint to crash recovery output.

The crashed worker output is plain text. Tag as text to satisfy MD040.

-```
+```text
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/lifecycle-and-onboarding.md` around lines 355 -
363, The crash recovery example in the lifecycle/onboarding spec is missing a
language hint on the fenced block, so update the crash output snippet to use the
correct markdown code fence language in the example around the worker crash log.
Make this change in the section containing the caller-worker restart output so
the rendered example is tagged as plain text and matches the MD040 requirement.

423-427: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add text language hint to lock fallback output.

The lock fallback output is plain text. Tag as text to satisfy MD040.

-```
+```text
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/lifecycle-and-onboarding.md` around lines 423 -
427, The lock fallback output in this section is missing a language hint, which
triggers the Markdown lint rule. Update the fenced block that shows the fallback
message under the state output to use a text language tag so the
`state`/`iii.lock` example is marked as plain text and satisfies MD040.

400-404: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add text language hint to cycle error output.

The cycle error output is plain text. Tag as text to satisfy MD040.

-```
+```text
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/lifecycle-and-onboarding.md` around lines 400 -
404, The cycle error output block is missing an explicit language hint, so
update the markdown snippet in the lifecycle-and-onboarding content to tag the
plain-text error example as text for MD040 compliance. Locate the cycle error
example near the compose dependency cycle message and change the fenced block to
use the text language identifier while keeping the existing error content
unchanged.

408-415: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add text language hint to port conflict output.

The port conflict error output is plain text. Tag as text to satisfy MD040.

-```
+```text
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/lifecycle-and-onboarding.md` around lines 408 -
415, The port conflict example in the lifecycle-and-onboarding docs is missing a
language hint, so update the fenced block around the ws://localhost bind error
output to use the text language tag. Locate the snippet containing the “cannot
bind ws://localhost:49134 — address already in use” message and change its
opening fence to a text-labeled fence so it satisfies MD040.

378-382: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add text language hint to orphan sweep output.

The orphan sweep output is plain text. Tag as text to satisfy MD040.

-```
+```text
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/lifecycle-and-onboarding.md` around lines 378 -
382, Add the missing text language hint to the orphan sweep output block in the
lifecycle-and-onboarding spec so the plain-text example is tagged correctly for
MD040. Update the fenced block around the orphaned worker process output to use
the appropriate text qualifier, keeping the existing content in place and
adjusting only the markdown fence formatting in that section.

46-59: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add text language hint to terminal output block.

The iii up target output is plain text. Tag as text to satisfy MD040.

-```
+```text
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/lifecycle-and-onboarding.md` around lines 46 - 59,
The terminal output block in the lifecycle-and-onboarding spec is missing the
required language hint, so update the fenced output example to use the text
label. Locate the plain output snippet shown for the iii up command and change
its code fence so it is explicitly tagged as text, satisfying MD040 without
altering the content.

91-103: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add text language hint to file tree listing.

The iii init quickstart output is a file tree. Tag as text to satisfy MD040.

-```
+```text
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/lifecycle-and-onboarding.md` around lines 91 - 103,
The quickstart file tree in the lifecycle-and-onboarding spec is missing an
explicit text language hint for the Markdown code block. Update the `iii init
quickstart` output snippet so the file tree fence is tagged as text, using the
existing quickstart listing in the document to locate the block, and keep the
rest of the tree unchanged.

323-329: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add text language hint to iii ps output.

The iii ps target output is plain text. Tag as text to satisfy MD040.

-```
+```text
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/lifecycle-and-onboarding.md` around lines 323 -
329, The `iii ps` example output is currently an unlabeled fenced block, which
triggers the MD040 lint rule. Update the fenced code block in the `iii ps`
section to use the `text` language hint so the plain-text sample is explicitly
tagged, keeping the content unchanged and ensuring the block is easy to locate
by its `iii ps`/process listing example.

386-393: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add text language hint to error output.

The missing dependency error output is plain text. Tag as text to satisfy MD040.

-```
+```text
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/lifecycle-and-onboarding.md` around lines 386 -
393, The compose error example is missing a language hint, so update the fenced
code block in the lifecycle-and-onboarding markdown to use the text language
tag. Keep the existing error output content, and change the block that shows the
worker-compose.yml dependency error so it is rendered as plain text for MD040
compliance.
tech-specs/2026-06-devexp/cli-and-functions.md (2)

132-132: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add language hint to CLI tree diagram.

The command tree at line 132 is plain text. Tag as text to satisfy MD040.

-```
+```text
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/cli-and-functions.md` at line 132, The CLI tree
diagram in the markdown spec is missing a language hint, so update the fenced
block around the command tree to use the text info string to satisfy MD040.
Locate the diagram in the CLI-and-functions documentation and change the
existing fence opening so the tree content is explicitly marked as text; keep
the diagram content unchanged.

272-278: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add language hint to architecture pseudo-code.

The thin-wrapper architecture block is pseudo-code. Tag as text to satisfy MD040.

-```
+```text
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/cli-and-functions.md` around lines 272 - 278, The
architecture pseudo-code block is missing an explicit language hint, so update
the fenced block in the CLI/functions spec to use a text label. Locate the
pseudo-code diagram around the worker parse/serialize/invoke flow and change the
opening fence to the text-annotated form so it satisfies MD040 while preserving
the existing content.
tech-specs/2026-06-devexp/process-daemon.md (3)

289-302: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add language hint to function surface spec.

The process::* function surface block is pseudo-code with Rust-like struct syntax. Tag as rust or text to satisfy MD040.

-```
+```rust
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/process-daemon.md` around lines 289 - 302, The
process function surface block is missing a language hint, so update the fenced
pseudo-code snippet in the process-daemon spec to use a Rust or plain-text
label. Keep the existing process::start/process::stop/process::restart and
related function signatures unchanged, and only adjust the code fence marker so
it satisfies MD040.

96-104: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add language hint to plain-text diagram.

The tier diagram at lines 96–104 lacks a language tag. Add text to satisfy MD040 and clarify it's not executable code.

-```
+```text
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/process-daemon.md` around lines 96 - 104, The tier
diagram block in process-daemon.md is missing a language hint, so update the
fenced diagram under the relevant section to use the text language tag. Keep the
existing content unchanged and ensure the fence around the TIER 0/1/2 diagram is
marked as text so the Markdown renderer and MD040 treat it as plain text instead
of executable code.

463-481: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add language hint to recovery sequence.

The daemon restart sequence is pseudo-code. Tag as text to satisfy MD040.

-```
+```text
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/process-daemon.md` around lines 463 - 481, The
recovery sequence in the process-daemon spec is a pseudo-code block that needs
an explicit language hint for Markdown compliance. Update the fenced block in
the restart/reconcile section to use a text code fence by tagging the existing
sequence with text, keeping the content unchanged, and verify the surrounding
section still renders correctly in the process::reconcile and daemon restart
flow.
tech-specs/2026-06-devexp/engine-and-gateway.md (1)

238-249: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add language hint to pseudo-code blocks.

Lines 238–249 and 410 are fenced code blocks without language tags. While these are pseudo-code, adding text or bash as the language hint silences MD040 and improves rendering consistency.

-```
+```text
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/engine-and-gateway.md` around lines 238 - 249, Add
language hints to the fenced pseudo-code blocks in the engine-and-gateway spec
so Markdown lint MD040 is silenced and rendering stays consistent. Update the
code fences around the STEP 0 engine startup flow and the later connected-path
pseudo-code (including the block near the referenced second occurrence) to use a
language tag such as text or bash, keeping the content unchanged while matching
the rest of the document’s formatting.
tech-specs/_gallery/src/components/schematic/ModeToggle.tsx (1)

14-15: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the toggle group's accessible name mandatory.

ModeToggle always renders role="group", but label is optional, so future call sites can create an unnamed interactive group. Requiring label here (or supporting aria-labelledby) closes that gap at the API boundary.

Proposed change
 interface ModeToggleProps<T extends string> {
   value: T
   onChange: (next: T) => void
   options: ModeToggleOption<T>[]
   className?: string
-  /** accessible name for the toggle group, e.g. "theme" */
-  label?: string
+  /** accessible name for the toggle group, e.g. "theme" */
+  label: string
 }

Also applies to: 29-32

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/_gallery/src/components/schematic/ModeToggle.tsx` around lines 14
- 15, Make the toggle group’s accessible name required in ModeToggle, since it
always renders a role="group" and should never be unnamed. Update the ModeToggle
prop/type definition to require label (or add explicit aria-labelledby support)
and adjust the ModeToggle render logic so every call site must provide a group
name. Also review the related usage in ModeToggle’s JSX to ensure the group’s
accessible labeling path is enforced consistently.
tech-specs/2026-06-devexp/presentation/src/sections/SystemMapSection.tsx (1)

6-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid a second source of truth for kind labels.

These labels already live in src/content/map.ts next to KIND_HUE. Deriving the legend from that shared data keeps the legend, node sheets, and future copy updates in sync.

Possible simplification
-import { KIND_HUE, MEETING_POINTS } from '`@/content/map`'
+import { KIND_HUE, KIND_LABEL, MEETING_POINTS } from '`@/content/map`'
 
-const LEGEND: Array<{ kind: keyof typeof KIND_HUE; label: string }> = [
-  { kind: 'plane', label: 'connection plane' },
-  { kind: 'brain', label: 'the brain' },
-  { kind: 'pid', label: 'pid owner' },
-  { kind: 'file', label: 'file' },
-  { kind: 'wk', label: 'worker' },
-]
+const LEGEND = (Object.keys(KIND_HUE) as Array<keyof typeof KIND_HUE>).map(
+  (kind) => ({ kind, label: KIND_LABEL[kind] }),
+)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/presentation/src/sections/SystemMapSection.tsx`
around lines 6 - 12, The legend in SystemMapSection is duplicating kind labels
instead of using the shared map data. Update the LEGEND definition to derive its
items from the same source used for KIND_HUE in src/content/map.ts, so
SystemMapSection stays in sync with node sheets and future label changes. Keep
the existing kind keys, but remove the hardcoded label list and build the legend
from the shared data model.
tech-specs/2026-06-devexp/presentation/src/content/map.ts (1)

23-49: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Type the map identifiers as a closed set.

id, from, and to are all plain string, but SystemMap/MapDatasheet treat them as if every value is guaranteed to exist. One typo in this dataset turns into a runtime crash instead of a type error.

Proposed direction
+export type MapNodeId =
+  | 'compose'
+  | 'lock'
+  | 'gw'
+  | 'config'
+  | 'ops'
+  | 'daemon'
+  | 'sandbox'
+  | 'w1'
+  | 'w2'
+  | 'w3'
+
 export interface MapNode {
-  id: string
+  id: MapNodeId
   x: number
   y: number
   w: number
   h: number
   title: string
@@
 export interface MapEdge {
   id: string
-  from: string
-  to: string
+  from: MapNodeId
+  to: MapNodeId
   d: string
   label: string

Then carry MapNodeId into the selected state in SystemMapSection.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/presentation/src/content/map.ts` around lines 23 -
49, Type the map identifiers as a closed set instead of plain strings in MapNode
and MapEdge so invalid ids are caught at compile time. Introduce a shared
MapNodeId union derived from the actual node ids in the map dataset, then update
id, from, and to to use that type throughout SystemMap, MapDatasheet, and any
lookup helpers that assume the ids always exist. Also propagate MapNodeId into
the selected state in SystemMapSection so selection can only reference valid
nodes.
tech-specs/2026-06-devexp/presentation/src/components/schematic/Button.tsx (1)

30-38: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Default the shared button primitive to type="button".

As written, this will submit by default when it lands inside a form. Shared UI buttons are safer if they opt out unless the caller explicitly passes type="submit".

Suggested fix
       <button
         ref={ref}
+        type="button"
         className={cn(
           'inline-flex items-center justify-center gap-x-2 whitespace-nowrap font-mono lowercase rounded-none transition-[background-color,color,border-color] duration-150 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-accent disabled:pointer-events-none disabled:opacity-40 select-none cursor-pointer',
           variantClasses[variant],
           sizeClasses[size],
           className,
         )}
         {...props}
       >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/presentation/src/components/schematic/Button.tsx`
around lines 30 - 38, The shared Button primitive currently inherits the browser
default submit behavior inside forms, so update the Button component to default
its rendered button type to “button” unless the caller explicitly provides a
type. Make this change in the Button function/component where props are spread
onto the underlying button element, and ensure any caller-supplied type still
overrides the default.
tech-specs/2026-06-devexp/presentation/src/sections/Hero.tsx (1)

22-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the shared hero thesis instead of retyping it here.

src/content/changes.ts already exports HERO_THESIS, but this paragraph duplicates the same copy locally. That gives the section two sources of truth and makes spec updates easy to miss.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/presentation/src/sections/Hero.tsx` around lines 22
- 29, Replace the duplicated hero paragraph in Hero.tsx with the shared
HERO_THESIS from src/content/changes.ts so the section uses one source of truth.
Update the Hero section to reference the existing exported thesis instead of
hardcoding the copy locally, keeping the surrounding markup and emphasis spans
aligned with the shared content.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3f10f3d6-8570-47b3-9134-ae71d1bbf19d

📥 Commits

Reviewing files that changed from the base of the PR and between 70c387d and c60c1a4.

⛔ Files ignored due to path filters (3)
  • tech-specs/2026-06-devexp/presentation/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • tech-specs/_gallery/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • tech-specs/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (96)
  • tech-specs/.gitignore
  • tech-specs/.nvmrc
  • tech-specs/2026-06-devexp/README.md
  • tech-specs/2026-06-devexp/cli-and-functions.md
  • tech-specs/2026-06-devexp/configuration-and-bootstrap.md
  • tech-specs/2026-06-devexp/devex-review-improvements.md
  • tech-specs/2026-06-devexp/engine-and-gateway.md
  • tech-specs/2026-06-devexp/lifecycle-and-onboarding.md
  • tech-specs/2026-06-devexp/migration.md
  • tech-specs/2026-06-devexp/presentation/.gitignore
  • tech-specs/2026-06-devexp/presentation/README.md
  • tech-specs/2026-06-devexp/presentation/index.html
  • tech-specs/2026-06-devexp/presentation/package.json
  • tech-specs/2026-06-devexp/presentation/src/App.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/Footer.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/PageShell.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/PlayerControls.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/Section.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/SpecSheet.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/TopNav.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/diagrams/CliPlayground.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/diagrams/DaemonModel.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/diagrams/MeetingPointsSequence.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/diagrams/ReadinessGraph.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/diagrams/SystemMap.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/Button.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/Caret.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/Cell.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/CodeBlock.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/FnChip.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/ModeToggle.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/Prompt.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/Sheet.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/StatusDot.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/StatusPanel.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/Terminal.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/Wordmark.tsx
  • tech-specs/2026-06-devexp/presentation/src/components/schematic/WorkerCard.tsx
  • tech-specs/2026-06-devexp/presentation/src/content/changes.ts
  • tech-specs/2026-06-devexp/presentation/src/content/map.ts
  • tech-specs/2026-06-devexp/presentation/src/content/playground.ts
  • tech-specs/2026-06-devexp/presentation/src/hooks/useHashRoute.ts
  • tech-specs/2026-06-devexp/presentation/src/hooks/useStepper.ts
  • tech-specs/2026-06-devexp/presentation/src/hooks/useTheme.ts
  • tech-specs/2026-06-devexp/presentation/src/index.css
  • tech-specs/2026-06-devexp/presentation/src/lib/utils.ts
  • tech-specs/2026-06-devexp/presentation/src/main.tsx
  • tech-specs/2026-06-devexp/presentation/src/pages/ComposePage.tsx
  • tech-specs/2026-06-devexp/presentation/src/pages/MigrationPage.tsx
  • tech-specs/2026-06-devexp/presentation/src/pages/PlaygroundPage.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/ComposeSection.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/ConfigSection.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/DaemonSection.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/Hero.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/MeetingPointsSection.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/PayoffSection.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/PlaygroundSection.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/ReadinessSection.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/RemovedSection.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/SystemMapSection.tsx
  • tech-specs/2026-06-devexp/presentation/src/sections/WhySection.tsx
  • tech-specs/2026-06-devexp/presentation/src/vite-env.d.ts
  • tech-specs/2026-06-devexp/presentation/tsconfig.app.json
  • tech-specs/2026-06-devexp/presentation/tsconfig.json
  • tech-specs/2026-06-devexp/presentation/tsconfig.node.json
  • tech-specs/2026-06-devexp/presentation/vite.config.ts
  • tech-specs/2026-06-devexp/process-daemon.md
  • tech-specs/2026-06-devexp/secrets.md
  • tech-specs/2026-06-devexp/worker-compose.md
  • tech-specs/README.md
  • tech-specs/_gallery/.gitignore
  • tech-specs/_gallery/index.html
  • tech-specs/_gallery/package.json
  • tech-specs/_gallery/src/App.tsx
  • tech-specs/_gallery/src/components/Gallery.tsx
  • tech-specs/_gallery/src/components/PresentationCard.tsx
  • tech-specs/_gallery/src/components/SiteFooter.tsx
  • tech-specs/_gallery/src/components/SiteHeader.tsx
  • tech-specs/_gallery/src/components/schematic/ModeToggle.tsx
  • tech-specs/_gallery/src/components/schematic/Prompt.tsx
  • tech-specs/_gallery/src/components/schematic/Sheet.tsx
  • tech-specs/_gallery/src/components/schematic/StatusDot.tsx
  • tech-specs/_gallery/src/components/schematic/Wordmark.tsx
  • tech-specs/_gallery/src/content/presentations.ts
  • tech-specs/_gallery/src/hooks/useTheme.ts
  • tech-specs/_gallery/src/index.css
  • tech-specs/_gallery/src/lib/utils.ts
  • tech-specs/_gallery/src/main.tsx
  • tech-specs/_gallery/src/vite-env.d.ts
  • tech-specs/_gallery/tsconfig.app.json
  • tech-specs/_gallery/tsconfig.json
  • tech-specs/_gallery/tsconfig.node.json
  • tech-specs/_gallery/vite.config.ts
  • tech-specs/build.mjs
  • tech-specs/package.json
  • tech-specs/vercel.json

@@ -0,0 +1,153 @@
@import "tailwindcss";

@theme {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Tailwind v4 directives are still lint-failing here.

Stylelint is flagging @theme and each @utility as unknown, so this file will keep failing static analysis until the lint config is updated to allow Tailwind v4 directives.

#!/bin/bash
set -euo pipefail

echo "== Tailwind v4 directives used by the gallery stylesheet =="
rg -n '`@theme`|`@utility`' tech-specs/_gallery/src/index.css

echo
echo "== Stylelint config candidates =="
fd -HI '(.stylelintrc.*|stylelint.config.*)$' . -x sh -c '
  echo "=== $1 ==="
  sed -n "1,220p" "$1"
' sh {}

echo
echo "== Existing unknown-at-rule / Tailwind exceptions =="
rg -n 'at-rule-no-unknown|scss/at-rule-no-unknown|ignoreAtRules|tailwindcss' . \
  -g '.stylelintrc*' -g 'stylelint.config.*' -g 'package.json'

Also applies to: 111-142

🧰 Tools
🪛 Stylelint (17.13.0)

[error] 3-3: Unexpected unknown at-rule "@theme" (scss/at-rule-no-unknown)

(scss/at-rule-no-unknown)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/_gallery/src/index.css` at line 3, Stylelint is still treating the
Tailwind v4 directives in the gallery stylesheet as unknown at-rules, so this
file will keep failing lint until the config is updated. Update the Stylelint
configuration that governs tech-specs/_gallery/src/index.css to explicitly allow
the Tailwind directives used there, especially `@theme` and `@utility`, and verify
the exception is added in the relevant stylelint config or rule setup rather
than in the stylesheet itself.

Comment on lines +15 to +17
const stepper = useStepper(ORDER.length, 1500)
const frontier = stepper.step // index of the node currently coming up

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Model the root node as already ready.

READINESS_NODES defines configuration as the always-ready root, but this logic renders it as awaiting L1 on the first frame and reports 0 / 5 ready. The frontier needs to start after the root, otherwise the diagram contradicts its own data model.

Proposed fix
 export function ReadinessGraph({ className }: { className?: string }) {
   const stepper = useStepper(ORDER.length, 1500)
-  const frontier = stepper.step // index of the node currently coming up
+  const frontier = stepper.step + 1 // root is already ready

   return (
@@
         {ORDER.map((n, i) => {
           const ready = i < frontier
           const coming = i === frontier
@@
         <span className="ml-auto font-mono text-[11px] uppercase tracking-[0.06em] text-ink-faint tabular-nums">
-          {Math.min(frontier, ORDER.length)} / {ORDER.length} ready
+          {Math.min(frontier, ORDER.length)} / {ORDER.length} ready
         </span>

Also applies to: 25-27, 89-90

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tech-specs/2026-06-devexp/presentation/src/components/diagrams/ReadinessGraph.tsx`
around lines 15 - 17, The readiness animation in ReadinessGraph should treat the
root node as already ready instead of counting it as pending. Update the
frontier initialization/derivation in ReadinessGraph (the useStepper and
frontier logic) so it starts after the always-ready configuration node, and
adjust any readiness counts/labels derived from that frontier to exclude the
root from “awaiting” and “0 / 5 ready” states on the first frame. Make the
change consistently across the rendering and progress calculation paths that use
ORDER, frontier, and the node readiness display.

Comment on lines +17 to +37
/**
* hash routing with two namespaces: `#/...` paths are routes (deep-dive
* pages); bare `#section-id` hashes stay native anchor scrolls on the home
* page.
*/
export function useHashRoute(): Route {
const [route, setRoute] = useState<Route>(() => parse(window.location.hash))

useEffect(() => {
const onChange = () => setRoute(parse(window.location.hash))
window.addEventListener('hashchange', onChange)
return () => window.removeEventListener('hashchange', onChange)
}, [])

useEffect(() => {
// deep-dive pages and the explicit "#/" home link start at the top;
// bare "#section" hashes keep native anchor behaviour.
if (route.kind === 'page' || window.location.hash === '#/') {
window.scrollTo({ top: 0, behavior: 'instant' })
}
}, [route])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Bare #section links won't land on the section when leaving a deep-dive page.

Line 18 says bare hashes should keep native anchor scrolling, but that only works if the target element already exists. From #/playground, #/compose, or #/migration, the browser processes #why before Home is mounted, misses the anchor, and this hook never retries after route flips back to home, so the user lands on the wrong spot.

Suggested fix
 export function useHashRoute(): Route {
   const [route, setRoute] = useState<Route>(() => parse(window.location.hash))
+  const [hash, setHash] = useState(() => window.location.hash)

   useEffect(() => {
-    const onChange = () => setRoute(parse(window.location.hash))
+    const onChange = () => {
+      const nextHash = window.location.hash
+      setHash(nextHash)
+      setRoute(parse(nextHash))
+    }
     window.addEventListener('hashchange', onChange)
     return () => window.removeEventListener('hashchange', onChange)
   }, [])

   useEffect(() => {
     // deep-dive pages and the explicit "`#/`" home link start at the top;
     // bare "`#section`" hashes keep native anchor behaviour.
     if (route.kind === 'page' || window.location.hash === '`#/`') {
       window.scrollTo({ top: 0, behavior: 'instant' })
+      return
+    }
+
+    if (route.kind === 'home' && hash.startsWith('#') && !hash.startsWith('`#/`')) {
+      document.getElementById(hash.slice(1))?.scrollIntoView()
     }
-  }, [route])
+  }, [route, hash])

   return route
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* hash routing with two namespaces: `#/...` paths are routes (deep-dive
* pages); bare `#section-id` hashes stay native anchor scrolls on the home
* page.
*/
export function useHashRoute(): Route {
const [route, setRoute] = useState<Route>(() => parse(window.location.hash))
useEffect(() => {
const onChange = () => setRoute(parse(window.location.hash))
window.addEventListener('hashchange', onChange)
return () => window.removeEventListener('hashchange', onChange)
}, [])
useEffect(() => {
// deep-dive pages and the explicit "#/" home link start at the top;
// bare "#section" hashes keep native anchor behaviour.
if (route.kind === 'page' || window.location.hash === '#/') {
window.scrollTo({ top: 0, behavior: 'instant' })
}
}, [route])
/**
* hash routing with two namespaces: `#/...` paths are routes (deep-dive
* pages); bare `#section-id` hashes stay native anchor scrolls on the home
* page.
*/
export function useHashRoute(): Route {
const [route, setRoute] = useState<Route>(() => parse(window.location.hash))
const [hash, setHash] = useState(() => window.location.hash)
useEffect(() => {
const onChange = () => {
const nextHash = window.location.hash
setHash(nextHash)
setRoute(parse(nextHash))
}
window.addEventListener('hashchange', onChange)
return () => window.removeEventListener('hashchange', onChange)
}, [])
useEffect(() => {
// deep-dive pages and the explicit "`#/`" home link start at the top;
// bare "`#section`" hashes keep native anchor behaviour.
if (route.kind === 'page' || window.location.hash === '`#/`') {
window.scrollTo({ top: 0, behavior: 'instant' })
return
}
if (route.kind === 'home' && hash.startsWith('#') && !hash.startsWith('`#/`')) {
document.getElementById(hash.slice(1))?.scrollIntoView()
}
}, [route, hash])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/presentation/src/hooks/useHashRoute.ts` around
lines 17 - 37, The bare hash anchor handling in useHashRoute is missing a retry
after navigating back from a deep-dive page, so native scrolling can fire before
the Home content is mounted. Update the useHashRoute logic so that when
parse(window.location.hash) resolves to home with a bare section hash, the hook
performs the anchor scroll only after the home view is rendered, using the
existing route state and the hashchange-driven setRoute flow to trigger a second
attempt if needed. Keep the current special handling for Route.kind === 'page'
and '`#/`' intact, but make sure bare section hashes reliably scroll to their
target when leaving pages like playground, compose, or migration.

Comment on lines +1 to +3
@import "tailwindcss";

@theme {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Stylelint config files =="
fd -HI '(.stylelintrc.*|stylelint.config.*|package.json)$' .

echo
echo "== Rules touching unknown at-rules or Tailwind =="
rg -n -C2 'at-rule-no-unknown|scss/at-rule-no-unknown|ignoreAtRules|tailwind' \
  $(fd -HI '(.stylelintrc.*|stylelint.config.*|package.json)$' .)

Repository: iii-hq/iii

Length of output: 3911


Update Stylelint configuration to recognize Tailwind v4 at-rules.

The project explicitly depends on Tailwind v4 (tailwindcss@^4.3.0, @tailwindcss/vite@^4.3.0), but no Stylelint configuration file (.stylelintrc or stylelint.config) was found to whitelist the new at-rules. Consequently, @theme and @utility trigger scss/at-rule-no-unknown errors. Create a Stylelint config ignoring these specific rules or install a Tailwind-compatible linter plugin.

Diff context
`@import` "tailwindcss";

`@theme` {
🧰 Tools
🪛 Stylelint (17.13.0)

[error] 3-3: Unexpected unknown at-rule "@theme" (scss/at-rule-no-unknown)

(scss/at-rule-no-unknown)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/presentation/src/index.css` around lines 1 - 3, The
Stylelint setup does not recognize Tailwind v4 at-rules, so add or update the
Stylelint configuration to whitelist Tailwind-specific directives used by the
CSS entrypoint. Make the change in the project’s Stylelint config (such as
.stylelintrc or stylelint.config) so that `@theme` and `@utility` are ignored by
scss/at-rule-no-unknown, or wire in a Tailwind-compatible Stylelint plugin that
covers these rules. Reference the CSS entrypoint that uses `@import` "tailwindcss"
and `@theme` to verify the config fixes the lint errors.

Source: Linters/SAST tools

Comment on lines +58 to +61
<StatusPanel
variant="success"
headline="phase 2 is already shipped in part"
detail="all seven built-in workers self-register their schema + initial value at boot via configuration::register and read the configuration store — there is no seed and no config: block to migrate. configuration is owned end-to-end by the configuration worker."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Don't describe config migration as manual re-entry.

This copy says there is “no seed” / “no config: block to migrate” and tells operators to re-apply tuned values with iii worker config set. The established contract is that worker config.yaml blocks are seed-only and, once persisted, are stripped with a breadcrumb to ./data/configuration/<id>.yaml. As written, this changes the migration semantics and suggests previously tuned values are discarded. As per path instructions, "config.yaml blocks for workers are seed-only and should be stripped once their value is persisted; on next boot, keep the - name: entry but replace the dead config: block with a breadcrumb pointing to ./data/configuration/<id>.yaml."

Also applies to: 75-76

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/2026-06-devexp/presentation/src/pages/MigrationPage.tsx` around
lines 58 - 61, The MigrationPage copy is incorrectly describing worker
configuration migration as if there is no seed or config block and as if
operators must re-enter values manually. Update the StatusPanel and related
migration text in MigrationPage to reflect the established contract: worker
config.yaml blocks are seed-only, persisted values are stripped from the seed,
and the remaining worker entry should keep the - name: line with a breadcrumb to
./data/configuration/<id>.yaml instead of suggesting manual re-entry or
discarded tuned values.

Source: Path instructions

Comment thread tech-specs/build.mjs
Comment on lines +53 to +58
// --no-frozen-lockfile so a slightly-stale committed lockfile self-heals
// instead of hard-failing the deploy. CI (Vercel sets CI=true) otherwise
// defaults to frozen, which aborts on any lockfile/package.json drift — a
// confusing "builds locally, fails on Vercel" trap for future decks.
if (!SKIP_INSTALL)
run('pnpm', ['install', '--ignore-workspace', '--no-frozen-lockfile'], dir)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Don't let deploys resolve an unreviewed dependency graph.

--no-frozen-lockfile makes the Vercel build silently regenerate dependency resolution when a manifest drifts, so production can ship packages that were never committed or reviewed. Keep frozen installs for CI/deploys and only allow the self-healing path behind an explicit local-only opt-in if you still want that escape hatch.

Suggested change
 function buildProject(dir, label) {
   console.log(`\n▸ ${label}`)
-  // --no-frozen-lockfile so a slightly-stale committed lockfile self-heals
-  // instead of hard-failing the deploy. CI (Vercel sets CI=true) otherwise
-  // defaults to frozen, which aborts on any lockfile/package.json drift — a
-  // confusing "builds locally, fails on Vercel" trap for future decks.
-  if (!SKIP_INSTALL)
-    run('pnpm', ['install', '--ignore-workspace', '--no-frozen-lockfile'], dir)
+  if (!SKIP_INSTALL) {
+    const installArgs = ['install', '--ignore-workspace']
+    if (!process.env.CI) installArgs.push('--no-frozen-lockfile')
+    run('pnpm', installArgs, dir)
+  }
   run('pnpm', ['build'], dir)
 }

As per path instructions, "Check all PRs for security issues and intentional supply chain compromises / malicious code."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// --no-frozen-lockfile so a slightly-stale committed lockfile self-heals
// instead of hard-failing the deploy. CI (Vercel sets CI=true) otherwise
// defaults to frozen, which aborts on any lockfile/package.json drift — a
// confusing "builds locally, fails on Vercel" trap for future decks.
if (!SKIP_INSTALL)
run('pnpm', ['install', '--ignore-workspace', '--no-frozen-lockfile'], dir)
if (!SKIP_INSTALL) {
const installArgs = ['install', '--ignore-workspace']
if (!process.env.CI) installArgs.push('--no-frozen-lockfile')
run('pnpm', installArgs, dir)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tech-specs/build.mjs` around lines 53 - 58, The install step in build should
not silently refresh the dependency graph during deploys; `run` in the
`build.mjs` flow is currently passing `--no-frozen-lockfile`, which can let
CI/Vercel regenerate lockfile resolution from an unreviewed manifest drift.
Update the install path to use frozen lockfile behavior by default for
deploy/CI, and if you keep a self-healing escape hatch, gate it behind an
explicit local-only opt-in around the existing `SKIP_INSTALL` / install logic so
production builds never resolve dependencies implicitly.

Source: Path instructions

Comment on lines +152 to +154
│ ├── start <W>... [--no-wait] # runtime → process-daemon
│ ├── stop <W>... [-y|--yes] [-t|--timeout <s>]
│ ├── restart <W>... [--no-wait]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's remove it, compose up/down/restart already manipulates individual workers

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's think about this regarding sandbox

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's move iii-sandbox to be an external worker (and remove iii- prefix) which means iii doesn't need to manage anything anymore, we can manage through iii trigger sandbox::

Comment on lines +155 to +156
│ ├── logs <W> [-f|--follow] [-n|--tail <N>] [--since <dur>]
│ ├── status <W> [--watch] [--json] # default ONE-SHOT; --watch = live TUI

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's remove it, compose status/logs already pulls from individual workers

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's think about this regarding sandbox

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's move iii-sandbox to be an external worker (and remove iii- prefix) which means iii doesn't need to manage anything anymore, we can manage through iii trigger sandbox::

│ ├── restart <W>... [--no-wait]
│ ├── logs <W> [-f|--follow] [-n|--tail <N>] [--since <dur>]
│ ├── status <W> [--watch] [--json] # default ONE-SHOT; --watch = live TUI
│ ├── ps [--json] # NEW: process table across managed procs

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's remove it, compose ps already pulls from individual workers

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's think about this regarding sandbox

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's move iii-sandbox to be an external worker (and remove iii- prefix) which means iii doesn't need to manage anything anymore, we can manage through iii trigger sandbox::

Comment on lines +145 to +148
│ ├── add <SRC>... [-f|--force] [--up] [--no-wait]
│ │ # SRC = name[@ver] | oci-ref | ./path ; edits compose+lock, does NOT auto-start
│ ├── update [W]... [-f|--force] [--clear-artifacts] [--restart] [--no-wait]
│ ├── remove <W>... [--clear-artifacts] [-y|--yes]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's think about this

│ ├── logs <W> [-f|--follow] [-n|--tail <N>] [--since <dur>]
│ ├── status <W> [--watch] [--json] # default ONE-SHOT; --watch = live TUI
│ ├── ps [--json] # NEW: process table across managed procs
│ ├── exec <W> [-e K=V] [-w CWD] [-t|--tty] [--timeout <ms>] -- CMD...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's move iii-sandbox to be an external worker (and remove iii- prefix) which means iii doesn't need to manage anything anymore, we can manage through iii trigger sandbox::

Comment on lines +152 to +153
│ ├── start <W>... [--no-wait] # runtime → process-daemon
│ ├── stop <W>... [-y|--yes] [-t|--timeout <s>]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's move iii-sandbox to be an external worker (and remove iii- prefix) which means iii doesn't need to manage anything anymore, we can manage through iii trigger sandbox::

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant